home *** CD-ROM | disk | FTP | other *** search
- /*
- HASPrinting.c from Hsoi's App Shell © 1995-1997 John C. Daub. All rights reserved.
-
- This file contains the various routines to deal with printing text (and other
- stuff like embedded objects). There are a few routines called elsewhere in
- the code to deal with printing (like PrOpen, etc), but otherwise, we're
- here!
-
- This code isn't final, and is going through a lot of changes to find the optimal
- way to print. right now, it's a combination of code based upon my own writing,
- Tom Bender's Tex-Edit+ 1.6.3 printing routines, and Inside Macintosh: Imaging with
- QuickDraw's sample code pp. 9-20 to 9-22.
-
- How it's set will print and work for you, but just informing you that it's in
- a state of flux.
- */
-
- #pragma mark ••• #includes •••
-
- #ifndef _WASTE_
- #include "WASTE.h"
- #endif
- #include "HASGlobals.h"
- #ifndef __HSOIS_APP_SHELL__
- #include "HASMain.h"
- #endif
- #include "HASMiscEvents.h"
- #include "HASPrinting.h"
- #include "HASUtilPrint.h"
- #include "HASUtilities.h"
- #include "HASUtilPStrings.h"
- #include "WETabs.h"
-
- #ifndef __PROCESSES__
- #include <Processes.h>
- #endif
-
- #ifndef _STDIO
- #include <stdio.h>
- #endif
-
- #include "WASTE_Objects.h"
-
- #pragma mark -
- #pragma mark ••• Globals •••
-
- // local globals
-
- static DialogRef myPrintStatusDialog;
- static Str255 sDocumentName; // name of the document being printed (used in the idleProc)
- static Str255 sMsgStr1; // message displayed in the idle proc dialog
- static Str255 sMsgStr2; // ditto
- static short sNumOfPages; // number of pages required to print the document
- static short sNumOfCopies; // number of copies to print
- static short sFirstPage; // first page number
- static short sLastPage; // last page number
- PrIdleUPP printIdleUPP = nil; // our print idle procedure
-
-
- #pragma mark -
- #pragma mark ••• Initialization •••
-
- /*
- when this function is called, there was an error in calling PrOpen during our
- HsoiInitPrintingStuff routine. Here's what I'm trying to combat:
-
- I remember once I hit upon a strange problem. I've had window size based upon
- the paper/page size from the gPrinterRecord structure. well, what if there is
- some bogus values in the gPrinterRecord?
-
- here's what happened to me.
-
- I was testing the app shell on a brand new PowerMac that I got into my office.
- when i fired the app up, there was no window, or at least, no window that i
- could see. there technically was a window there (due to how the rest of the
- app was behaving) but i think the coordinates of the window (as obtained from/based on
- the gPrinterRecord page/paper sizes) were so fubar that you didn't see any window.
-
- I found the problem for this: the computer had never had a printer selected
- in it; it was a brand new computer...the Chooser had never been visited. once
- i went to the Chooser and selected a printer, the app worked just fine.
-
- So, this is a problem that must be dealt with (albeit a rare problem), else
- we risk confusing the user cause their windows will seem to not be there, or
- at least be some strange (and probably unusable) size.
-
- Of course, what would be best would be that when we obtain the paper/page size
- from the gPrinterRecord, that we could do some sort of integrity check on those
- values...but how can we? I can't think of any sort of way considering the
- vast number of printer drivers out there, and the lack of standardization with them.
-
- So, here's what we do...we attempt to find out if a printer has ever been selected.
-
- I went screwing around in the System file one day and discovered this. In 'STR '
- -8192, it contains the name of the current printer. That could be enough to look
- for, but it's not. I did an installation of a clean pure System 7.1 and found something
- already in there (as a default value I assume...fyi, it was the Imagewriter).
-
- But, i did find the lack of something in the clean System file: there's a resource,
- 'alis' (for alias I'm sure), and lo and behold, it had only one resource in it,
- ID number -8192. I checked my normal System file and in this, I found an alias
- record pointing to my current selected printer!
-
- after doing some toying around, I found this was the key to solving my problem.
- just check for an 'alis' resource in the System file, and (just to be safe)
- check for an ID of -8192. We might even go a little further and get the 'STR '
- -8192 and compare these 2 for similarity, but I think that's unneeded.
-
- So that is what we do.
-
- Now, I must put in this note of caution and note of disclaimer.
-
- I scoured NIM:Imaging with QuickDraw's chapter on printing. almost no where in
- there did i see anything talking about what to do in this sort of situation. I
- did see one thing on page 9-75 saying the most common error encountered (when
- calling PrError()) is PAPNoPrinter (-4101): the printer is not found, is closed,
- or is not selected. when this error is encountered, you should display an alert
- box asking the user to select a printer from the Chooser.
-
- Now, I did a Find search on this error code throughout all the Universal Header
- files. no where did i find this error code. Plus, in NIM:QD, it says this is
- a LaserWriter error code (so who knows what other printer drivers, especially
- non-Apple drivers) would return in this sort of instance. I have Deskwriter
- drivers, and when I was testing all this, my call to PrOpen then PrError returned
- an error of -43, fnfErr (file not found).
-
- So, I'm sorta taking an "undocumented" approach to discovering this information.
- Sure, I'll look for PAPNoPrinter errors, but I can't count on them showing up. So,
- my searching for the 'alis' resource in the System file might be considered a
- "no no" by Apple, and they might not like that I am doing this sort of technique.
-
- Furthermore, since this seems to be undocumented, who knows if it will continue
- to be Apple's way of doing things (all things considered, I"m sure this won't
- change in future system software releases, but you never know).
-
- So, this technique might break in the future, Apple might ask me (and/or you) to
- remove this kind of code, who knows....basically, USE THIS TECHNIQUE AT YOUR
- OWN RISK!!! If Apple asks you not to do this, ask them then just how TO do
- it properly (cause apparently PAPNoPrinter isn't always the error code in this
- instance). And I ask that to Apple also....if you don't want me to do this sort
- of thing, then just how am I TO properly do it?
-
- So follow along...hopefully this will work....
-
- the jist is this: first of all, HsoiCheckPrinterDriver (and it's associated
- routines) are ONLY called during the HsoiInitPrintingStuff() routine, and then
- only if we receive an error from PrError immediately after calling PrOpen (to
- validate/printdefault the gPrinterRecord). If there is an error, we'll try
- to see what that error might be and deal with it properly. We'll give the
- user an alert box letting them know what's up, and a few options:
-
- to quit the program immediately and launch the Chooser (a nice "touch" allowing
- them to immediately select a printer...note, we'll have no way of really knowing
- if they select a printer or not...we just have to hope the user will).
-
- to just quit and forget about it.
-
- those are the options....i could impliment something that we just skip it
- and let the user play, but don't give the ability to print (and make windows
- some arbitrary size), but i think that's too much of a pain.
-
- if you want to see this in action, you could step through with a debugger and
- just change variable values to "appropriate" values to force the alert to come
- up, or to REALLY try this, do this: (and you do this at your own risk...it will
- involve modifying your System file, so if you mess something up, please don't
- blame me)
-
- make a copy of your System file and open it up in ResEdit. delete the 'alis'
- resource. remove the original System file from the system folder and put your
- copy (renamed to "System") in your System folder. restart your computer.
- launch the shell demo and watch what happens. if you want to see how bad things
- could be if this alert didn't come up, comment out the call to HsoiCheckPrinterDriver
- in HsoiInitPrintingStuff, recompile and try the above again...you'll see how small
- and mooshed your window gets.
-
- */
-
- void HsoiCheckPrinterDriver( OSErr prErr )
- {
- Handle h;
-
- // first, let's check what that error is. if the error is greater than
- // 0, there could be some problems, but they seem to only deal with the PrGeneral
- // procedure (or it was the iPrAbort error, which shouldn't be here in this
- // situation). there could also be a simple memFullErr (-108), but we're gonna
- // be a bit lazy and not bother with that right now.
-
- // first, just in case we got a noErr passed here, we'll just return
-
- if ( prErr == noErr )
- return;
-
- // if the error is the PAPNoPrinter error, we know we'll have to do the
- // alert box
-
- else if ( prErr == -4101 ) // -4101 is PAPNoPrinter
- HsoiDoChooserAlert( prErr );
- else
- {
- // ok..not that error, so, to simplify things (i.e. be lazy *grin*), we'll
- // just take all other errors as a problem....
-
- // make the System file the current resource file
-
- UseResFile( 0 );
-
- // try to get the 'alis' resource. we'll use Get1Resource instead of GetResource
- // cause we only want to search the current resource file (the system file) and
- // not every open resource fork (like our application's)
-
- h = Get1Resource( 'alis', -8192 );
-
- // just to be good, we'll make sure we switch the curr res file to our
- // application's before we continue
-
- UseResFile( gAppResourceFork );
-
- // if h is nil, we'll assume it doesn't exist (tho h being nil might be because
- // we don't have enough memory...we should call ResError to check, but again, we're
- // being simple).
-
- if ( h == nil )
- {
- HsoiDoChooserAlert( prErr );
- }
- else
- // well, we'll dump the resource, but there's some other problem....
-
- HsoiForgetResource( &h );
- }
-
- // and that should be all she wrote...we should (technically) never reach
- // this far...
-
- return;
-
- }
-
- // this procedure is called by the above function. if we ran into some error,
- // we put up a dialog saying there was a problem and to select a printer in the
- // Chooser and we even give the user the option of us auto-launching the
- // Chooser for them. no matter what happens tho, the app will quit.
-
- // originally, i wrote this function using LaunchApplication. however, since the
- // Chooser is technically a desk accessory (and how might this change in Copland?),
- // we cannot use LaunchApplication, we must use LaunchDeskAccessory (which kinda sucks
- // cause it's not as robust as LaunchApplication). However, to demonstrate how to use
- // LaunchApplication (and in case the Chooser changes in kind/type in the future), i'll
- // leave in all the relevant code to launch the Chooser if it was an application. it'll
- // be commented out by C style comments instead of C++ comments (i.e. the /* */ instead
- // of the //)
-
- void HsoiDoChooserAlert( OSErr prErr )
- {
- short itemHit;
- /* LaunchParamBlockRec launchParams; */
- FSSpec fileSpec;
- Str255 errStr;
- OSErr err;
- FInfo info;
- Boolean isFolder, wasAlias;
-
-
- // first, we must display our alert to determine what to do
-
- // make the error into a string for display in the alert
-
- NumToString( (long)prErr, errStr );
-
- ParamText( errStr, NIL_STRING, NIL_STRING, NIL_STRING );
-
- // do the alert
-
- itemHit = StopAlert( rChooserAlert, nil );
-
- // now, based upon itemHit, what to do
-
- // if itemHit == -1, the alert wasn't able to be displayed, so we'll just bail out.
-
- if ( itemHit == -1 )
- {
- SysBeep(1);
- ExitToShell();
- }
-
- // if they choose to quit
-
- if ( itemHit == cancel ) // "Quit" is item 2, same as cancel
- ExitToShell(); // bail out
-
- // else itemHit == ok (the Quit and Auto-Launch Chooser stuff)
-
- // since we know already that we're running under system 7 (at least), we know
- // the Process Manager exists, so we can use LaunchApplication to launch the
- // Chooser.
-
- // we need to initialize the launchParams variable before we use it. one thing
- // we need is the FSSpec to the Chooser, so let's get it
-
- // first, let's find the Apple Menu items folder on the current system disk.
- // there might be a chance the Chooser isn't in the Apple Menu Items folder, but
- // i'd think that'd be about a 1 in a million chance (and hell, maybe we should
- // check for such a case, but eh....we're being simple and just assuming a lot)
-
- err = FindFolder(kOnSystemDisk, kAppleMenuFolderType, kDontCreateFolder,
- &fileSpec.vRefNum, &fileSpec.parID);
-
- // put the name of the Chooser in the file spec
-
- HsoipStringCopy( "\pChooser", fileSpec.name );
-
- // now we'll make sure the Chooser exists
-
- err = FSpGetFInfo( &fileSpec, &info );
-
- // deal with a few error situations
-
- if ( err != noErr )
- ExitToShell(); // didn't find it? skip it
-
- // now, check to make sure this is is the real Chooser and NOT an alias. if it is
- // an alias to the Chooser, we'll resolve the alias before we proceed
-
- err = ResolveAliasFile( &fileSpec, true, &isFolder, &wasAlias );
-
- /*
- // and now we can initialize the launchParams struct
-
- launchParams.launchBlockID = extendedBlock;
- launchParams.launchEPBLength = extendedBlockLen;
- launchParams.launchFileFlags = 0;
- launchParams.launchControlFlags = launchNoFileFlags;
- launchParams.launchAppSpec = &fileSpec;
- launchParams.launchAppParameters = nil;
-
- // and finally, launch the Chooser! (note, because of our launchControlFlags, our
- // app will automatically quit). but just in case some sort of error occurs, we'll
- // call ExitToShell()
-
- if ( LaunchApplication( &launchParams ) )
- ExitToShell();
- */
-
- // now we'll launch the Chooser. the way LaunchDeskAccessory works can greatly vary
- // and I recommend you read NIM:Processes chapter 2 (esp. 2-30) to get more information.
- // I'll just explain what I'm doing.
-
- // LaunchDA searches the resource fork of the file specified in the fileSpec for
- // a 'DRVR' resource with the given name (a StringPtr passed as the second argument).
- // if it's found, it's launched. if nil is passed as the given name argument (as I
- // am doing), then the first 'DRVR' resource found is the one that is launched. In
- // this case here, there should only be one resource (one named: Chooser), but this
- // sort of thing allows flexibility if in the given fileSpec there are more than one
- // 'DRVR' resources.
-
- // so, launch it...
-
- err = LaunchDeskAccessory( &fileSpec, nil );
-
- // and then immediately can the app....if we could use LaunchApplication, this wouldn't
- // be necessary cause in the launchControlFlags of the LaunchParamBlockRec, we have
- // not specified to continue our app (by passing launchContinue), therefore it'd
- // autoterminate. but, this is how DA's work...and it'd be really nice if in the
- // future, Apple could put some sort of similar functionality in LaunchDA.
-
- ExitToShell();
-
- return;
- }
-
-
- // let's get everything initialized...called at app startup time
-
- OSErr HsoiInitPrintingStuff( void )
- {
- OSErr err = noErr;
-
- // get a handle allocated for the print record
-
- gPrinterRecord = (THPrint)NewHandleClear( sizeof(TPrint) );
-
- if ( gPrinterRecord != nil )
- {
- // ok..we got a printer record!
-
- // it's gonna hang around for the life of the program, so let's move it high
- // in the heap so to minimize memory fragmentation. And let's lock it down
- // too just to be safe.
-
- HLockHi( (Handle)gPrinterRecord );
-
- // now that we got it, let's initialize it to some default values
-
- PrOpen();
-
- err = PrError();
-
- if ( err )
- {
- // we got an error, and this error could affect the entire operation
- // of the application, therefore we ought to check a few more things...
-
- HsoiCheckPrinterDriver( err );
-
- // and just in case something goes wrong, we know there is a problem
- // and possibly one worthy of quitting out anyways...but it's probably
- // better to display some sort of error alert to catch odd situations
-
- ExitToShell();
- }
-
- PrintDefault( gPrinterRecord );
- PrClose(); // don't need to leave it open :)
- }
- else
- err = memFullErr;
-
- return err;
- }
-
-
- #pragma mark -
- #pragma mark ••• Page Setup •••
-
- // this handles the "File:Page Setup" menu item
-
- void HsoiDoPageSetup( void )
- {
- WindowRef window = FrontWindow();
-
- // if there's a sound playing, stop it
-
- if ( SoundIsPlaying() )
- StopCurrentSound();
-
- // open up the printer driver
-
- PrOpen();
-
- // fix the cursor to the arrow
-
- InitCursor();
-
- // present the user with the Printer Style (Page Setup) dialog
-
- if ( PrError() == noErr )
- {
- // make sure the front window is deactivated
-
- HsoiDoActivate( 0, window );
- PrStlDialog( gPrinterRecord );
- }
-
- // we're done...close up the printer driver
-
- PrClose();
-
- // and we need to redraw our windows...
-
- // what we really ought to do in here is check to see if the user selected some
- // new paper size...if so, we should redraw the window in the new size to reflect
- // the changes. I think the easiest way to deal with this would simply be to
- // make a copy of the gPrinterRecord before the call to PrStlDialog so we can
- // preserve the "original" settings. then, just call something like:
-
- // if ( !EqualRect( &(*gPrinterRecord)->prInfo.rPage, &(*printRecCopy)->prInfo.rPage ) )
- // HsoiDoReadjustWindowSize();
-
- // or something....however, before I impliment something like that, we have to
- // take into account all the various printer drivers and how they manage things.
- // with my HP Deskwriter (driver version 6.0), if i change the paper size, the
- // page orrientation, or reduce/enlarge the page, it affects the rPage size.
- // but, to keep WYSIWYG, just how will that affect the screen window? and
- // how to take variations in all the different printer drivers into account?
-
- // this is something that needs to be done, but hopefully someone will standardize
- // the way printer drivers work to make our lives easier.
-
- HsoiDoActivate( 1, window );
-
- return;
- }
-
- #pragma mark -
- #pragma mark ••• Printing Utils •••
-
- Boolean HsoiOpenPrintManager( WindowRef window )
- {
- OSErr err = noErr;
- Boolean retval = false;
-
- // make sure we have a cursor
-
- InitCursor();
-
- // only proceed if we have a properly initialized print record
-
- if ( gPrinterRecord != nil )
- {
- // open the printer and check for errors
-
- PrOpen();
- err = PrError();
-
- // if no errors, bring up the Print dialog
-
- if ( err == noErr )
- {
-
- // stop any currently playing sounds
-
- if ( SoundIsPlaying() )
- StopCurrentSound();
-
- // determine the number of pages required to print the document
-
- sNumOfPages = HsoiDetermineNumOfPages( HsoiGetWindowWE( window ));
-
- // deactivate the front window
-
- HsoiDoActivate( 0, window );
-
- // throw up the print dialog
-
- if ( PrJobDialog( gPrinterRecord ) )
- {
-
- // let's install our print idle proc. you have to insert the
- // idle proc before calling PrOpenDoc() (but after such calls as
- // PrJobDialog(), which can reset the value of the pIdleProc field
- // to nil
-
- if ( printIdleUPP == nil )
- printIdleUPP = NewPrIdleProc( hsoiMyPrintIdleProc );
- (*gPrinterRecord)->prJob.pIdleProc = printIdleUPP;
-
- // initialize the GrafPort before printing
-
- gThePrinterPort = PrOpenDoc( gPrinterRecord, nil, nil );
-
- // check for errors
-
- err = PrError();
-
- // if the printing GrafPort is active, return as such.
-
- if ( err == noErr )
- retval = true;
- else
- // else, no active one, so just close the printer port
- PrClose();
- }
- else // they selected cancel so just close
-
- PrClose();
- }
- else // there was an error, handle it
- goto exit;
- }
-
- // if there was an error, display it as such
-
- exit:
- if ( err != noErr )
- HsoiDoError( rPrintingStrings, strPrintError, err, kErrStop );
-
- return retval;
- }
-
- // this function figures out the dimensions of the actual printed text on
- // the page. takes the margin settings and an "empty" printRect as arguments, and
- // the dimensions of the printRect will be set within it (it's a pointer...no need
- // to return it)
-
- // incidentally, the figurePageNumbers Boolean is used as an programmer controled
- // check as to whether or not the page number adjustment should be made or not.
- // in the printing loop, this should be set to true (and then prefs setting can
- // determine if this adjustment should be made or not). this should be set to
- // false for example when called by HsoiCalcIdealWindowSize() in which we don't
- // want to bother with this calculation (otherwise in HsoiCalcIdealWindowSize()
- // i'd have to reverse this calculation, if it even occured, and that's too much
- // trouble...this is much easier)
-
- void HsoiGetPrintRect( Rect *margins, LongRect *printRect, Boolean figurePageNumbers )
- {
- LongRect paperRect, maxPrintableRect;
-
- // get the size of the paper
-
- WERectToLongRect( &(*gPrinterRecord)->rPaper, &paperRect );
-
- // now get the printer's maximum print rectangle
-
- WERectToLongRect( &(*gPrinterRecord)->prInfo.rPage, &maxPrintableRect );
-
- // and inset the actual print rectangle by the size of the margins, if the
- // margins are big enough.
- // procedure: set the printRect.xxx to paperRect.xxx with margins.xxx, and
- // check to make sure it doesn't (over) extend things
-
- printRect->left = paperRect.left + margins->left;
- if ( (printRect->left < maxPrintableRect.left) || (printRect->left > maxPrintableRect.right) )
- printRect->left = maxPrintableRect.left;
-
- printRect->top = paperRect.top + margins->top;
- if ( (printRect->top < maxPrintableRect.top) || (printRect->top > maxPrintableRect.bottom) )
- printRect->top = maxPrintableRect.top;
-
- printRect->right = paperRect.right - margins->right;
- if ( (printRect->right > maxPrintableRect.right) || (printRect->right < printRect->left) )
- printRect->right = maxPrintableRect.right;
-
- printRect->bottom = paperRect.bottom - margins->bottom;
- if ( (printRect->bottom > maxPrintableRect.bottom) || (printRect->bottom < printRect->top) )
- printRect->bottom = maxPrintableRect.bottom;
-
- // leave some room for the page number at the bottom, if needed
-
- if ( figurePageNumbers && (gMyPrefs.printPageNumbers) &&
- ( (maxPrintableRect.bottom - printRect->bottom) < kSpaceForPageNumber) )
- printRect->bottom = maxPrintableRect.bottom - kSpaceForPageNumber;
-
- return;
- }
-
- WEReference HsoiCloneTextHandle( LongRect *printRect, WEReference we )
- {
- LongRect emptyViewRect = { 0,0,0,0 };
- long selLength;
- Handle hText;
- StScrpHandle hStyl;
- WESoupHandle hSoup;
- SignedByte currentJust;
- OSErr err;
- WEReference scratchWE;
-
- // create a new WEReference using the printRect. viewRect is closed cause
- // GX doesn't recognize ClipRect()
-
- err = WENew( printRect, &emptyViewRect, weDoUseTempMem, &scratchWE );
-
- // use same tab/align settings as the window being printed
-
- if ( err == noErr )
- {
- currentJust = WEGetAlignment( we );
- WESetAlignment( currentJust, scratchWE );
- if ( WEIsTabHooks( we ) )
- err = WEInstallTabHooks( scratchWE );
- }
-
- // make a copy of the current selection's text/styles
-
- if ( err == noErr )
- {
- // allocate some memory for the handles to the text, style, and soup
-
- HsoiNewHandleTemp( 0, &hText );
- HsoiNewHandleTemp( 0, (Handle *)&hStyl );
- HsoiNewHandleTemp( 0, (Handle *)&hSoup );
-
- // get the range that we want to copy (in this case, the whole thing)
-
- selLength = WEGetTextLength( we );
-
- err = WECopyRange( 0, selLength, hText, hStyl, hSoup, we );
- }
-
- // insert the text into our scratch area, re-open the viewRect
-
- if ( err == noErr )
- {
- HLock( hText );
- err = WEInsert( *hText, selLength, hStyl, hSoup, scratchWE );
- HUnlock( hText );
- WESetViewRect( printRect, scratchWE );
- }
-
- // and free up memory
-
- if ( hStyl != nil )
- HsoiForgetHandle( (Handle *)&hStyl );
- if ( hSoup != nil )
- HsoiForgetHandle( (Handle *)&hSoup );
- if ( hText != nil )
- HsoiForgetHandle( (Handle *)&hText );
-
- if ( err == noErr )
- return scratchWE;
- else
- return nil;
- }
-
- // HsoiInchesToDots takes a string containing a number (like our prefs margin settings)
- // and converts it to pixels (dpi) based upon the printer's resolution. since we're
- // dealing with longs (and not doubles) most of the time, upon a return, the casting
- // of the double to a long basically discards the decimal point. all in all, a fraction
- // of a pixel shouldn't matter.
-
- long HsoiInchesToDots( Str255 inches, short direction )
- {
- #pragma unused (direction)
-
- long whole;
-
- double value;
-
- if ( pos( "\p.", inches ) > 0 )
- {
-
- sscanf( (char *)inches + 1, "%lf", &value );
-
- // if ( direction == kHortz )
- // return (long)(value * (*gPrinterRecord)->prInfo.iHRes );
- // else if ( direction == kVert )
- // return (long)(value * (*gPrinterRecord)->prInfo.iVRes );
- // else if ( direction == kForce72dpi )
- // return (long)(value * kDotsPerInch );
- // else // a "kludgy" default just in case a strange value is passed for direction
- return (long)(value * kDotsPerInch );
-
- }
- else // no decimal portion
- {
- StringToNum( inches, &whole );
-
- // if ( direction == kHortz )
- // return ( whole * (*gPrinterRecord)->prInfo.iHRes );
- // else if ( direction == kVert )
- // return ( whole * (*gPrinterRecord)->prInfo.iVRes );
- // else if ( direction == kForce72dpi )
- // return ( whole * kDotsPerInch );
- // else
- return ( whole * kDotsPerInch );
- }
-
- }
-
-
-
-
- void HsoiPrintFooter( long *currentPage, WindowRef window )
- {
- Str255 pageStr = "\p";
- short oldFont, oldSize, fontNum;
-
- // get the old port settings
-
- oldFont = GetWindowPort(window)->txFont;
- oldSize = GetWindowPort(window)->txSize;
-
- // center on printer's maximum print area
-
- MoveTo( (*gPrinterRecord)->prInfo.rPage.right / 2,
- (*gPrinterRecord)->prInfo.rPage.bottom - 3 );
-
- // print using the default font (set in the prefs)
-
- GetFNum( gMyPrefs.defFont, &fontNum );
- TextFont( fontNum );
- TextSize( kDefaultSize );
-
- // print the page number
-
- NumToString( *currentPage, pageStr );
- DrawString( pageStr );
-
- // restore the port and increment the page counter
-
- TextFont( oldFont );
- TextSize( oldSize );
-
- *currentPage += 1;
-
- return;
- }
-
-
- OSErr HsoiPrintUntilDone( Rect *margins, LongRect *printRect, WEReference we, WindowRef window )
- {
- LongRect paperRect, r, destRect;
- Rect shortRect;
- long totalTextLines, totalHeight, currentLine = 1, currentPage = 1,
- scrollAmount, viewHeight;
- Boolean abortedPrinting = false;
- OSErr err = noErr;
- RgnHandle printRgn;
-
- // get the number of lines in our text
-
- totalTextLines = WECountLines( we );
-
- // adjust the bottom of our text's destRect
-
- totalHeight = WEGetHeight( 0, totalTextLines, we );
- WEGetDestRect( &r, we );
- r.bottom = r.top + totalHeight;
- WESetDestRect( &r, we );
-
- // now print page by page until aborted or out of text...one page per loop
-
- while ( (!abortedPrinting) && ( currentLine <= totalTextLines ) )
- {
- // prepare to print a page
-
- PrOpenPage( gThePrinterPort, nil );
- scrollAmount = 0;
-
- // open the clipping rect so text will draw
-
- shortRect = (*gPrinterRecord)->prInfo.rPage;
- ClipRect( &shortRect );
-
- // calculate the height of the viewRect
-
- WEGetViewRect( &r, we );
- viewHeight = r.bottom - r.top + 1;
-
- // figure out how many lines per page
-
- while ( ((scrollAmount + WEGetHeight( currentLine - 1, currentLine, we )) <= viewHeight )
- && ( currentLine <= totalTextLines ) )
- {
- scrollAmount += WEGetHeight( currentLine - 1, currentLine, we );
- currentLine++;
- }
-
- // adjust the bottom of the printed page based on t he number of lines printed
-
- WEGetViewRect( &r, we );
- WERectToLongRect( &(*gPrinterRecord)->rPaper, &paperRect );
- if ( (paperRect.top + margins->top) < printRect->top )
- r.bottom = printRect->top + scrollAmount;
- else
- r.bottom = paperRect.top + margins->top + scrollAmount;
- WESetViewRect( &r, we );
-
- // insert the page number
-
- if ( gMyPrefs.printPageNumbers )
- HsoiPrintFooter( ¤tPage, window );
-
- // and finally actually print the text!
-
- WEGetViewRect( &r, we );
- WELongRectToRect( &r, &shortRect );
- printRgn = NewRgn();
- RectRgn( printRgn, &shortRect );
- WEUpdate( printRgn, we );
- if ( printRgn != nil )
- DisposeRgn( printRgn );
-
- /* NOW!! you must pay attention to this!!!
-
- Usually in a print routine, after calling WEUpdate (or TEUpdate or whatever
- you do to actually print the thing), you'd do something like this:
-
- ClipRect( &zeroRect ); // where zeroRect == { 0, 0, 0, 0 }
-
- WEScroll( 0, -scrollAmount, we );
-
- now, what that does is scroll the text up a page so we can print the next
- page. You call ClipRect() with a zeroRect to prevent WEScroll from redrawing
- the text as it's being scrolled. This is a technique gleaned from TextEdit
- (from what I understand...I've always used WASTE for my text stuff).
-
- BUT!!! this is a problem! Why? well, it makes some assumptions about the
- inner workings of WEScroll...and with the 1.2a4 version of the WASTE API,
- WEScroll changed how it works...with versions of WASTE prior to 1.2a4, you
- were able to get away (somewhat) with the ClipRect-WEScroll method, but no
- longer can you. Besides, from what I understand, calling WEScroll() in a
- printing loop can cause problems with certain printer drivers (according to
- Marco Piovanelli, this problem arises with the HP Deskwriter 6.0 driver, but
- I use that driver at home, and I've not had a problem *shrug* I'll take Marco's
- word for it).
-
- Let me include here some snippets of an email message between Marco and me
- regarding this subject (this is Marco "talking"):
-
- ----
-
- Believe me, you don't want to use WEScroll in a printing loop.
- WEScroll calls the Toolbox routine ScrollRect(), which I think goes
- through the QuickDraw bottlenecks set up by the printer driver.
- Okay, you tell me you cleverly work around the ScrollRect() call
- by setting the clip region to the empty region right before calling
- WEScroll. That might seem a good move, but it makes assumptions
- on the inner workings of WEScroll. In fact, starting from WASTE 1.2a4
- WEScroll sometimes calls ScrollRect() and sometimes calls WEUpdate with a
- NULL updateRgn, depending on the extent of hOffset/vOffset (ScrollRect
- is used only if there's some overlap). Now, WEUpdate(NULL, we) overrides
- your clip region and you promptly get the second page superimposed onto
- the first one.
-
- Fix: never *ever* call WEScroll from within a print loop!
- WEScroll doesn't just update the destination rectangle: it also *draws*
- into your port because that's exactly what it's meant to do.
- If what you need is really only changing the destination rectangle
- without causing anything to be drawn, replace WEScroll with something
- like:
-
- WEGetDestRect(&destRect, we);
- WEOffsetLongRect(&destRect, hOffset, vOffset);
- WESetDestRect(&destRect, we);
-
- Easy enough, isn't it?
-
- ----
-
- So, what do you do? Well, this technique is a better overall technique anyways
- for printing, and it's pretty simple. I also think it's really nice cause
- there's a lot less overhead in calling these 3 functions instead of WEScroll
- and ClipRect.
-
- */
- // first, we get the destRect of the WE instance
-
- WEGetDestRect( &destRect, we );
-
- // then we "scroll" to the next page by just offsetting the
- // rect's coordinates
-
- WEOffsetLongRect( &destRect, 0, -scrollAmount );
-
- // then we reinsert this new offsetted destRect back into the WE
- // instance. now we're on the next page!
-
- WESetDestRect( &destRect, we );
-
- // reset the bottom of the text's view rect to the full page
-
- WEGetViewRect( &r, we );
- r.bottom = printRect->bottom;
- WESetViewRect( &r, we );
-
- // look for a cancelation
-
- err = PrError();
- // if ( err != noErr )
- // {
- // abortedPrinting = true;
- // if ( err == iPrAbort )
- // err = noErr;
- // }
-
- if ( err == iPrAbort )
- {
- abortedPrinting = true;
- err = noErr;
- }
-
-
- // done with that page
-
- PrClosePage( gThePrinterPort );
-
- } // end: while ( (!abortedPrinting) && (currentLine <= totalTextLines) )
-
- return err;
- }
-
-
- // based upon the size of the printed page (the rPage field of the TPrint record),
- // determine how many pages will be required to print the document
-
- short HsoiDetermineNumOfPages( WEReference we )
- {
- short numPages = 0;
- Rect margins;
- LongRect r;
- LongRect printRect, paperRect, destRect;
- WEReference scratchWE;
- long totalHeight, totalTextLines, currentLine = 1;
- long viewHeight;
- long scrollAmount;
-
- // this is a bit complex and will probably seem a bit unnecessary/overkill
- // considering how this is all used in the end, but this is how we must
- // do things to calc the number of pages.
-
- // first, we have to get some measurements....
-
- // set up the margins
-
- margins.left = HsoiInchesToDots( gMyPrefs.printLeftMargin, kHortz );
- margins.right = HsoiInchesToDots( gMyPrefs.printRightMargin, kHortz );
- margins.top = HsoiInchesToDots( gMyPrefs.printTopMargin, kVert );
- margins.bottom = HsoiInchesToDots( gMyPrefs.printBottomMargin, kVert );
-
- // get the dimensions of the actual printed rect
-
- HsoiGetPrintRect( &margins, &printRect, gMyPrefs.printPageNumbers );
-
- // create and work with a copy of the text
-
- scratchWE = HsoiCloneTextHandle( &printRect, we );
-
- // set the port in the scratchWE to the printer port
-
- WESetInfo( wePort, (Ptr)&gThePrinterPort, scratchWE );
-
- // if the user wants to print double spaced text, adjust as such
-
- if ( gMyPrefs.printDoubleSpaced )
- HsoiSetSpacing( scratchWE );
-
- // get the number of lines in our text
-
- totalTextLines = WECountLines( scratchWE );
-
- // adjust the bottom of our text's destRect
-
- totalHeight = WEGetHeight( 0, totalTextLines, scratchWE );
- WEGetDestRect( &r, scratchWE );
- r.bottom = r.top + totalHeight;
- WESetDestRect( &r, scratchWE );
-
- // loop through, counting up the number of pages
-
- while ( currentLine <= totalTextLines )
- {
- scrollAmount = 0;
-
- // calculate the height of the viewRect
-
- WEGetViewRect( &r, scratchWE );
- viewHeight = r.bottom - r.top + 1;
-
- // figure out how many lines per page
-
- while ( ((scrollAmount + WEGetHeight( currentLine - 1, currentLine, scratchWE )) <= viewHeight )
- && ( currentLine <= totalTextLines ) )
- {
- scrollAmount += WEGetHeight( currentLine - 1, currentLine, scratchWE );
- currentLine++;
- }
-
- // adjust the bottom of the printed page based on t he number of lines printed
-
- WEGetViewRect( &r, scratchWE );
- WERectToLongRect( &(*gPrinterRecord)->rPaper, &paperRect );
- if ( (paperRect.top + margins.top) < printRect.top )
- r.bottom = printRect.top + scrollAmount;
- else
- r.bottom = paperRect.top + margins.top + scrollAmount;
- WESetViewRect( &r, scratchWE );
-
- // get the destRect of the WE instance
-
- WEGetDestRect( &destRect, scratchWE );
-
- // then we "scroll" to the next page by just offsetting the
- // rect's coordinates
-
- WEOffsetLongRect( &destRect, 0, -scrollAmount );
-
- // then we reinsert this new offsetted destRect back into the WE
- // instance. now we're on the next page!
-
- WESetDestRect( &destRect, scratchWE );
-
- // reset the bottom of the text's view rect to the full page
-
- WEGetViewRect( &r, scratchWE );
- r.bottom = printRect.bottom;
- WESetViewRect( &r, scratchWE );
-
- // and finally, increment the page counter
-
- numPages++;
-
- } // end: while ( currentLine <= totalTextLines )
-
- if ( scratchWE != nil )
- WEDispose( scratchWE );
-
- return numPages;
- }
-
- #pragma mark -
- #pragma mark ••• Print •••
-
- void HsoiDoPrint( WindowRef windowToPrint )
- {
- WEReference we, scratchWE;
- LongRect printRect;
- TPrStatus thePrinterStatus;
- Rect margins;
- OSErr err;
- register short i;
- Str255 tempStr;
-
-
- // since these are global variables (at least to this file, hence static), we need
- // to reinitialize them to hold nil values so we don't end up with weird strings
- // everywhere...
-
- for ( i = 0; i <= 255; i++ )
- {
- sDocumentName[i] = nil;
- sMsgStr1[i] = nil;
- sMsgStr2[i] = nil;
- }
-
- myPrintStatusDialog = nil;
-
- // now, get the WE reference
-
- if ( windowToPrint != nil )
- we = HsoiGetWindowWE( windowToPrint );
- else
- we = nil;
-
- // put up the Print dialog and get the printer port set up.
- // if the user selected "OK", let's print!
-
- if ( HsoiOpenPrintManager( windowToPrint ) )
- {
- // set the cursor to the watch
-
- SetCursor( *gWaitCursor );
-
- // get the number of copies
-
- sNumOfCopies = (*gPrinterRecord)->prJob.iCopies;
-
- // get the first and last pages
-
- sFirstPage = (*gPrinterRecord)->prJob.iFstPage;
- sLastPage = (*gPrinterRecord)->prJob.iLstPage;
-
- // reset to 1
-
- (*gPrinterRecord)->prJob.iFstPage = 1;
-
- // reset to the max
-
- (*gPrinterRecord)->prJob.iLstPage = iPrPgMax;
-
- // make sure we don't print page the last page
-
- if ( sNumOfPages < sLastPage )
- sLastPage = sNumOfPages;
-
- // make sure further output goes to the printer
-
- SetPort( (GrafPtr)gThePrinterPort );
-
- // allow fractional character widths for colums to line up properly
-
- SetFractEnable( true );
-
- // allow for the desired margins
-
- margins.left = HsoiInchesToDots( gMyPrefs.printLeftMargin, kHortz );
- margins.right = HsoiInchesToDots( gMyPrefs.printRightMargin, kHortz );
- margins.top = HsoiInchesToDots( gMyPrefs.printTopMargin, kVert );
- margins.bottom = HsoiInchesToDots( gMyPrefs.printBottomMargin, kVert );
-
- // get the dimensions of the actual printed rect
-
- HsoiGetPrintRect( &margins, &printRect, gMyPrefs.printPageNumbers );
-
- // create and work with a copy of the text
-
- scratchWE = HsoiCloneTextHandle( &printRect, we );
-
- // set the port in the scratchWE to the printer port
-
- err = WESetInfo( wePort, (Ptr)&gThePrinterPort, scratchWE );
-
- // if the user wants to print double spaced text, adjust as such
-
- if ( gMyPrefs.printDoubleSpaced )
- HsoiSetSpacing( scratchWE );
-
- // remove any selection range and deactivate the text
-
- WESetSelection( 0, 0, scratchWE );
- WEDeactivate( scratchWE );
-
- // and just before we really get into things, get our "printing in progress"
- // dialog set up (we installed the address of the proc earlier in
- // HsoiOpenPrintManager(), now we're just getting the stuff for it all
- // ready)
-
- // get the relevant text for the dialog...
-
- GetWTitle( windowToPrint, sDocumentName );
- GetIndString( tempStr, rPrintingStrings, strPrintBeginning );
- HsoiConcatString( sMsgStr1, tempStr );
- HsoiConcatString( sMsgStr1, sDocumentName );
- GetIndString( tempStr, rPrintingStrings, strPrintMiddle );
- HsoiConcatString( sMsgStr1, tempStr );
-
- GetIndString( sMsgStr2, rPrintingStrings, strPrintEnd );
-
- // let's now show the printing dialog
-
- myPrintStatusDialog = GetNewDialog( rPrintStatusDialog, nil, MOVE_TO_FRONT );
- ShowWindow( GetDialogWindow(myPrintStatusDialog) );
-
- // now, i'm not sure if this is a good thing to do or not (writing printing
- // code for Macintosh can be such an adventure!), but since not all
- // printer drivers support custom idle procs (and in which case, i'd fear
- // that since we overroad the default idle proc by installing our own, that
- // the user will have no way to cancel, not even cmd-.) i want to call
- // the idle proc at least once...this is cause it's the idle proc that
- // draws all our text in the dialog....if it's not called at least once,
- // the user will just see a blank dialog box, and that's no fun!
-
- hsoiMyPrintIdleProc();
-
- // and now, print until we're done!
-
- err = HsoiPrintUntilDone( &margins, &printRect, scratchWE, windowToPrint );
-
- // close the print port and print spooled data, if needed
-
- PrCloseDoc( gThePrinterPort );
-
- if ( ((*gPrinterRecord)->prJob.bJDocLoop == bSpoolLoop) && (PrError() == noErr) )
- PrPicFile( gPrinterRecord, nil, nil, nil, &thePrinterStatus );
-
- PrClose();
-
- // and now clean up and restore things as they should be
-
- if ( printIdleUPP != nil )
- DisposeRoutineDescriptor( printIdleUPP );
- DisposeDialog( myPrintStatusDialog );
- SetPortWindowPort( windowToPrint );
- SetFractEnable( false );
- if ( scratchWE != nil )
- WEDispose( scratchWE );
- InitCursor();
-
- } // end: if ( HsoiOpenPrintManager() )
-
- return;
- }
-
-
-
- // hmm..wonder if i'd want to put a rotating cursor in here...*shrug* it'd be easy,
- // but do i want to do it? sure...why not :-) but do it later
-
- // Don't give the dialog box a static text and ^0 cause those sorts of things
- // make the screen flicker.
-
- // For some reason, the cursor won't animate....maybe this is a place where I'll need
- // to put in a VBL task....
-
- pascal void hsoiMyPrintIdleProc( void )
- {
- GrafPtr oldPort;
- EventRecord event;
- char testChar;
- short dialogWidth;
- short dialogCenter;
- short strWidth;
- short halfStrWidth;
- short offset;
- Point currentPosition;
- FontInfo theFontInfo;
- register short lineHeight;
- short driverResFile;
-
- driverResFile = CurResFile();
- UseResFile( gAppResourceFork );
-
- // animate that cursor
-
- // RotateCursor( TickCount() );
-
- GetPort( &oldPort );
- SetGrafPortOfDialog( myPrintStatusDialog );
-
- // let's center the text in the status dialog
-
- // since we never know just how long the text could be (variable sizes/lengths
- // of the document's name), we'll have to do a little bit of centering stuff
-
- dialogWidth = GetWindowPort((WindowRef)myPrintStatusDialog)->portRect.right -
- GetWindowPort((WindowRef)myPrintStatusDialog)->portRect.left;
- dialogCenter = dialogWidth / 2;
-
- strWidth = StringWidth( sMsgStr1 );
- halfStrWidth = strWidth / 2;
-
- offset = dialogCenter - halfStrWidth;
-
- // the choice to move to 35 pixels vertically is arbitrary. it'd be nice to figure
- // out stuff to totally center both lines, now wouldn't it?
-
- MoveTo( offset, 35 );
- DrawString( sMsgStr1 );
-
- // now center the other string...
-
- // let's move down a line
-
- GetPen( ¤tPosition );
-
- GetFontInfo( &theFontInfo );
- lineHeight = theFontInfo.ascent + theFontInfo.descent + theFontInfo.leading;
-
- // and now center it horizontally
-
- strWidth = StringWidth( sMsgStr2 );
- halfStrWidth = strWidth / 2;
-
- offset = dialogCenter - halfStrWidth;
-
- // move the pen
-
- MoveTo( offset, currentPosition.v + lineHeight );
-
- // draw the string
-
- DrawString( sMsgStr2 );
-
- // now check for relevent events. in our limited case, we just want to
- // see if the user hit cmd-. to cancel (you could do more stuff in here, but do
- // be careful what you do...not everything is kosher to do in a print idle
- // procedure...for more information, see Inside Macintosh: Imaging with QuickDraw
- // and the chapter in there on Printing). As a note, if you do supply your own
- // custom print idle proc (like this), at the minimum you MUST check for cmd-. to
- // occur to cancel. other things to cancel might be that you put a cancel button
- // in your dialog, and you check for keyDown's on that button...
-
- // one BIG thing you'll notice is that we NEVER call PrError() in an idle proc.
- // this is a BIG NO NO!!!! there are many times that an error can and does occur
- // during printing, but this can be stuff meant solely to facilitate communication
- // between the computer and the printer...it is not meant for you nor the user!!
- // that is why what we do is sit around looking for a keyDown event...and if it's
- // a keyDown event, if there is the cmd key, and if the character is a period, we
- // call PrSetError( iPrAbort ) to put a cancel into the print error queue and then
- // we check for this error in HsoiPrintUntilDone().
-
- // if you decide to write/use an idle proc in your application, i highly recommend
- // reading about them in Inside Macintosh: Imaging with QuickDraw: Printing, or at
- // least read about them in THINK Reference (look up "TPrJob" and read the stuff at
- // the bottom of the entry)
-
- if ( GetNextEvent(keyDownMask, &event ) )
- {
- if ( (event.modifiers & cmdKey) != 0 )
- {
- testChar = event.message & charCodeMask;
- if ( testChar == '.' )
- PrSetError( iPrAbort );
- }
- }
-
-
- SetPort( oldPort );
-
- UseResFile( driverResFile );
-
- return;
- }
-
-